🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / core / network / src / commonTest / kotlin / org / meshtastic / core / network / radio / MockRadioTransportTest.kt
Displaying Raw • Download
core/network/src/commonTest/kotlin/org/meshtastic/core/network/radio/MockRadioTransportTest.kt 65a1f5ce4d0e4b8eafcd0bbfccafd41dc6ee888a (65a1f5ce) Text, 21.66 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.core.network.radio
Tff7b72import T7ee787kotlinx.coroutines.CoroutineScope
Tff7b72import T7ee787kotlinx.coroutines.cancel
Tff7b72import T7ee787kotlinx.coroutines.test.StandardTestDispatcher
Tff7b72import T7ee787kotlinx.coroutines.test.TestScope
Tff7b72import T7ee787kotlinx.coroutines.test.advanceTimeBy
Tff7b72import T7ee787kotlinx.coroutines.test.runCurrent
Tff7b72import T7ee787kotlinx.coroutines.test.runTest
Tff7b72import T7ee787okio.ByteString.Companion.encodeUtf8
Tff7b72import T7ee787okio.ByteString.Companion.toByteString
Tff7b72import T7ee787org.meshtastic.core.repository.HandshakeConstants
Tff7b72import T7ee787org.meshtastic.core.repository.RadioTransportCallback
Tff7b72import T7ee787org.meshtastic.core.repository.TransportDisconnectReason
Tff7b72import T7ee787org.meshtastic.proto.AdminMessage
Tff7b72import T7ee787org.meshtastic.proto.Data
Tff7b72import T7ee787org.meshtastic.proto.FromRadio
Tff7b72import T7ee787org.meshtastic.proto.HardwareModel
Tff7b72import T7ee787org.meshtastic.proto.MeshPacket
Tff7b72import T7ee787org.meshtastic.proto.PortNum
Tff7b72import T7ee787org.meshtastic.proto.Telemetry
Tff7b72import T7ee787org.meshtastic.proto.ToRadio
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertFalse
Tff7b72import T7ee787kotlin.test.assertNotNull
Tff7b72import T7ee787kotlin.test.assertTrue
T8b949e/**
* Demo Mode's contract with the app.
*
* The regression these tests exist for: the mock used to replay one combined frame array for *any* `want_config_id`, so
* stage 2 re-sent `my_info`, which resets the app's handshake state machine and makes the stage-2 `config_complete_id`
* get rejected. The handshake then never finished and Demo Mode showed an empty node list, no messages and an empty map
* — in every build, debug included.
*/
Tff7b72class T56d364MockRadioTransportTest Tb4b4b4{
Tff7b72private Tff7b72class T56d364RecordingCallback Tb4b4b4: Te6edf3RadioTransportCallback Tb4b4b4{
Tff7b72var Te6edf3connects Tff7b72= T79c0ff0
Tff7b72val Te6edf3received Tff7b72= Te6edf3mutableListOfTff7b72<Te6edf3FromRadioTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72override Tff7b72fun Td2a8ffonConnectTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3connectsTff7b72+Tff7b72+
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffonDisconnectTb4b4b4(Te6edf3isPermanentTb4b4b4: Tffa657BooleanTb4b4b4, Te6edf3errorMessageTb4b4b4: Tffa657String?Tb4b4b4, Te6edf3reasonTb4b4b4: Te6edf3TransportDisconnectReason?Tb4b4b4) Tff7b72=
Tffa657Unit
Tff7b72override Tff7b72fun Td2a8ffhandleFromRadioTb4b4b4(Te6edf3bytesTb4b4b4: Te6edf3ByteArrayTb4b4b4) Tb4b4b4{
Te6edf3receivedTb4b4b4.Te6edf3addTb4b4b4(Te6edf3FromRadioTb4b4b4.Te6edf3ADAPTERTb4b4b4.Te6edf3decodeTb4b4b4(Te6edf3bytesTb4b4b4)Tb4b4b4)
Tb4b4b4}
Tff7b72val Te6edf3nodeInfos
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3receivedTb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3node_info Tb4b4b4}
Tff7b72val Te6edf3completions
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3receivedTb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3config_complete_id Tb4b4b4}
Tff7b72val Te6edf3packets
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3receivedTb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3packet Tb4b4b4}
Tff7b72fun Td2a8ffpacketsOnTb4b4b4(Te6edf3portNumTb4b4b4: Te6edf3PortNumTb4b4b4) Tff7b72= Te6edf3packetsTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3decodedTff7b72?.Te6edf3portnum Tff7b72=Tff7b72= Te6edf3portNum Tb4b4b4}
Tb4b4b4}
T8b949e/**
* The transport must not share the test's own scope: its live-telemetry loop never completes, so `runTest` would
* wait on it forever. A cancellable child scope on the test scheduler keeps virtual time shared but lets the test
* end.
*/
Tff7b72private Tff7b72fun Te6edf3TestScopeTb4b4b4.Td2a8fftransportScopeTb4b4b4(Tb4b4b4) Tff7b72= Te6edf3CoroutineScopeTb4b4b4(Te6edf3StandardTestDispatcherTb4b4b4(Te6edf3testSchedulerTb4b4b4)Tb4b4b4)
Tf0883e@Test
Tff7b72fun Td2a8ff`start signals onConnect without emitting frames`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3callback Tff7b72= Te6edf3RecordingCallbackTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3scope Tff7b72= Te6edf3transportScopeTb4b4b4(Tb4b4b4)
Tff7b72try Tb4b4b4{
Te6edf3MockRadioTransportTb4b4b4(Te6edf3callbackTb4b4b4, Te6edf3scopeTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4)Tb4b4b4.Te6edf3startTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3callbackTb4b4b4.Te6edf3connectsTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`config stage answers with my_info and its own nonce but never with node info`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3callback Tff7b72= Te6edf3RecordingCallbackTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3scope Tff7b72= Te6edf3transportScopeTb4b4b4(Tb4b4b4)
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3transport Tff7b72= Te6edf3MockRadioTransportTb4b4b4(Te6edf3callbackTb4b4b4, Te6edf3scopeTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3CONFIG_NONCETb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3count Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3my_info Tff7b72!Tff7b72= Tff7b72null Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffstage 1 must announce our nodeTa5d6ff"Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3firstNotNullOfOrNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3metadata Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffstage 1 must send metadataTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3any Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3configTff7b72?.Te6edf3lora Tff7b72!Tff7b72= Tff7b72null Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffstage 1 must send LoRa configTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3any Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3channel Tff7b72!Tff7b72= Tff7b72null Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffstage 1 must announce the primary channelTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3callbackTb4b4b4.Te6edf3nodeInfosTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffnode info belongs to stage 2 onlyTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3HandshakeConstantsTb4b4b4.Te6edf3CONFIG_NONCETb4b4b4)Tb4b4b4, Te6edf3callbackTb4b4b4.Te6edf3completionsTb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`node info stage does not re-send my_info because that would reset the handshake`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3callback Tff7b72= Te6edf3RecordingCallbackTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3scope Tff7b72= Te6edf3transportScopeTb4b4b4(Tb4b4b4)
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3transport Tff7b72= Te6edf3MockRadioTransportTb4b4b4(Te6edf3callbackTb4b4b4, Te6edf3scopeTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3CONFIG_NONCETb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3NODE_INFO_NONCETb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3none Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3my_info Tff7b72!Tff7b72= Tff7b72null Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffre-sending my_info breaks stage 2Ta5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3listOfTb4b4b4(Te6edf3HandshakeConstantsTb4b4b4.Te6edf3NODE_INFO_NONCETb4b4b4)Tb4b4b4, Te6edf3callbackTb4b4b4.Te6edf3completionsTb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`node info stage populates a mesh whose nodes all render`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3callback Tff7b72= Te6edf3RecordingCallbackTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3scope Tff7b72= Te6edf3transportScopeTb4b4b4(Tb4b4b4)
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3transport Tff7b72= Te6edf3MockRadioTransportTb4b4b4(Te6edf3callbackTb4b4b4, Te6edf3scopeTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3CONFIG_NONCETb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3NODE_INFO_NONCETb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3nodes Tff7b72= Te6edf3callbackTb4b4b4.Te6edf3nodeInfos
Te6edf3assertTrueTb4b4b4(Te6edf3nodesTb4b4b4.Te6edf3size Tff7b72>Tff7b72= Te6edf3MIN_DEMO_NODESTb4b4b4, Ta5d6ff"Ta5d6ffexpected a populated node list, got Tffd700${Te6edf3nodesTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3nodesTb4b4b4.Te6edf3sizeTb4b4b4, Te6edf3nodesTb4b4b4.Te6edf3distinctBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3num Tb4b4b4}Tb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6ffnode numbers must be uniqueTa5d6ff"Tb4b4b4)
Te6edf3nodesTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3node Tff7b72-Tff7b72>
Tff7b72val Te6edf3user Tff7b72= Te6edf3assertNotNullTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3userTb4b4b4, Ta5d6ff"Ta5d6ffnode Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff has no userTa5d6ff"Tb4b4b4)
T8b949e// An UNSET hardware model nulls the denormalised name columns, which flags the node as incomplete and
T8b949e// hides it from search.
Te6edf3assertTrueTb4b4b4(Te6edf3userTb4b4b4.Te6edf3hw_model Tff7b72!Tff7b72= Te6edf3HardwareModelTb4b4b4.Te6edf3UNSETTb4b4b4, Ta5d6ff"Ta5d6ffnode Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff has no hardware modelTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3userTb4b4b4.Te6edf3long_nameTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffnode Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff has no long nameTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3userTb4b4b4.Te6edf3short_nameTb4b4b4.Te6edf3isNotBlankTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffnode Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff has no short nameTa5d6ff"Tb4b4b4)
T8b949e// Without last_heard every node reads as offline and vanishes under the "online only" filter.
Te6edf3assertTrueTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3last_heard Tff7b72> T79c0ff0Tb4b4b4, Ta5d6ff"Ta5d6ffnode Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff has no last_heardTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3position Tff7b72= Te6edf3assertNotNullTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3positionTb4b4b4, Ta5d6ff"Ta5d6ffnode Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff has no positionTa5d6ff"Tb4b4b4)
T8b949e// Presence, not "not zero": a scaled-integer 0 is a real coordinate on the equator and the prime
T8b949e// meridian, so the sentinel form would reject a legitimately placed node.
Te6edf3assertNotNullTb4b4b4(Te6edf3positionTb4b4b4.Te6edf3latitude_iTb4b4b4, Ta5d6ff"Ta5d6ffnode Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff has no latitudeTa5d6ff"Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3positionTb4b4b4.Te6edf3longitude_iTb4b4b4, Ta5d6ff"Ta5d6ffnode Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff has no longitudeTa5d6ff"Tb4b4b4)
Te6edf3assertNotNullTb4b4b4(Te6edf3nodeTb4b4b4.Te6edf3device_metricsTff7b72?.Te6edf3battery_levelTb4b4b4, Ta5d6ff"Ta5d6ffnode Tffd700${Te6edf3nodeTb4b4b4.Te6edf3numTffd700}Ta5d6ff has no battery levelTa5d6ff"Tb4b4b4)
Tb4b4b4}
T8b949e// Checked across the mesh rather than per node, which is where "the coordinates are real" actually lives:
T8b949e// the demo is meant to be spread out enough that the map has something to fit, and a per-node non-zero
T8b949e// test never showed that anyway.
Te6edf3assertTrueTb4b4b4(
Te6edf3nodesTb4b4b4.Te6edf3distinctBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3positionTff7b72?.Te6edf3latitude_i Te6edf3to Tffa657itTb4b4b4.Te6edf3positionTff7b72?.Te6edf3longitude_i Tb4b4b4}Tb4b4b4.Te6edf3size Tff7b72=Tff7b72= Te6edf3nodesTb4b4b4.Te6edf3sizeTb4b4b4,
Ta5d6ff"Ta5d6ffevery demo node needs its own position or they stack on one map pinTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`an unrecognised want_config_id is ignored rather than answered with the wrong stage`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3callback Tff7b72= Te6edf3RecordingCallbackTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3scope Tff7b72= Te6edf3transportScopeTb4b4b4(Tb4b4b4)
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3transport Tff7b72= Te6edf3MockRadioTransportTb4b4b4(Te6edf3callbackTb4b4b4, Te6edf3scopeTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4Tb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`seeded traffic supplies a channel thread a direct thread positions and telemetry`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3callback Tff7b72= Te6edf3RecordingCallbackTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3scope Tff7b72= Te6edf3transportScopeTb4b4b4(Tb4b4b4)
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3transport Tff7b72= Te6edf3MockRadioTransportTb4b4b4(Te6edf3callbackTb4b4b4, Te6edf3scopeTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3CONFIG_NONCETb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Tff7b72val Te6edf3myNodeNum Tff7b72= Te6edf3assertNotNullTb4b4b4(Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3firstNotNullOfOrNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3my_info Tb4b4b4}Tb4b4b4)Tb4b4b4.Te6edf3my_node_num
Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3NODE_INFO_NONCETb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)
Te6edf3testSchedulerTb4b4b4.Te6edf3advanceTimeByTb4b4b4(Te6edf3SEED_WINDOW_MSTb4b4b4)
Tff7b72val Te6edf3texts Tff7b72= Te6edf3callbackTb4b4b4.Te6edf3packetsOnTb4b4b4(Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4)
Tff7b72val Te6edf3broadcasts Tff7b72= Te6edf3textsTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3to Tff7b72=Tff7b72= Te6edf3BROADCAST_ADDR Tb4b4b4}
Tff7b72val Te6edf3directs Tff7b72= Te6edf3textsTb4b4b4.Te6edf3filter Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3to Tff7b72=Tff7b72= Te6edf3myNodeNum Tb4b4b4}
Te6edf3assertTrueTb4b4b4(Te6edf3broadcastsTb4b4b4.Te6edf3size Tff7b72>Tff7b72= T79c0ff2Tb4b4b4, Ta5d6ff"Ta5d6ffexpected a channel conversation, got Tffd700${Te6edf3broadcastsTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff messagesTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3directsTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffexpected a direct-message threadTa5d6ff"Tb4b4b4)
T8b949e// A broadcast has to land on the announced primary channel or it opens an unnamed "Channel N" thread.
Te6edf3assertTrueTb4b4b4(Te6edf3broadcastsTb4b4b4.Te6edf3all Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3channel Tff7b72=Tff7b72= T79c0ff0 Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffchannel messages must use the primary channelTa5d6ff"Tb4b4b4)
T8b949e// Across every frame, not just the texts: all of these ids come from one shared counter, and the app keys
T8b949e// its lists by packet id — a repeat is dropped at best and a duplicate-key crash at worst.
Tff7b72val Te6edf3all Tff7b72= Te6edf3callbackTb4b4b4.Te6edf3packets
Te6edf3assertEqualsTb4b4b4(Te6edf3allTb4b4b4.Te6edf3sizeTb4b4b4, Te6edf3allTb4b4b4.Te6edf3distinctBy Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3id Tb4b4b4}Tb4b4b4.Te6edf3sizeTb4b4b4, Ta5d6ff"Ta5d6ffpacket ids must be unique across the whole sessionTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3callbackTb4b4b4.Te6edf3packetsOnTb4b4b4(Te6edf3PortNumTb4b4b4.Te6edf3POSITION_APPTb4b4b4)Tb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffexpected position packetsTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3telemetry Tff7b72= Te6edf3callbackTb4b4b4.Te6edf3packetsOnTb4b4b4(Te6edf3PortNumTb4b4b4.Te6edf3TELEMETRY_APPTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3telemetryTb4b4b4.Te6edf3size Tff7b72>Tff7b72= T79c0ff2Tb4b4b4, Ta5d6ff"Ta5d6ffexpected device telemetry from several nodesTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3decoded Tff7b72= Te6edf3telemetryTb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3decodedTff7b72?.Te6edf3payloadTff7b72?.Te6edf3letTb4b4b4(Te6edf3TelemetryTb4b4b4.Te6edf3ADAPTERTff7b72::Te6edf3decodeTb4b4b4) Tb4b4b4}
Te6edf3assertTrueTb4b4b4(Te6edf3decodedTb4b4b4.Te6edf3any Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3device_metrics Tff7b72!Tff7b72= Tff7b72null Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffexpected device metricsTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3environment Tff7b72= Te6edf3decodedTb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3environment_metrics Tb4b4b4}
Te6edf3assertTrueTb4b4b4(
Te6edf3environmentTb4b4b4.Te6edf3any Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3temperature Tff7b72!Tff7b72= Tff7b72null Tff7b72&Tff7b72& Tffa657itTb4b4b4.Te6edf3relative_humidity Tff7b72!Tff7b72= Tff7b72null Tb4b4b4}Tb4b4b4,
Ta5d6ff"Ta5d6ffthe Environment tab needs temperature AND humidity on the same telemetryTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`received packets look like direct LoRa receptions so signal metrics reach the node list`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3callback Tff7b72= Te6edf3RecordingCallbackTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3scope Tff7b72= Te6edf3transportScopeTb4b4b4(Tb4b4b4)
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3transport Tff7b72= Te6edf3MockRadioTransportTb4b4b4(Te6edf3callbackTb4b4b4, Te6edf3scopeTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3CONFIG_NONCETb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3NODE_INFO_NONCETb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)
Te6edf3testSchedulerTb4b4b4.Te6edf3advanceTimeByTb4b4b4(Te6edf3SEED_WINDOW_MSTb4b4b4)
Tff7b72val Te6edf3positions Tff7b72= Te6edf3callbackTb4b4b4.Te6edf3packetsOnTb4b4b4(Te6edf3PortNumTb4b4b4.Te6edf3POSITION_APPTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3positionsTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4)
T8b949e// The app only harvests SNR/RSSI from LoRa packets, and only infers a hop count when hop_start is set.
Te6edf3assertTrueTb4b4b4(
Te6edf3positionsTb4b4b4.Te6edf3all Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3transport_mechanism Tff7b72=Tff7b72= Te6edf3MeshPacketTb4b4b4.Te6edf3TransportMechanismTb4b4b4.Te6edf3TRANSPORT_LORA Tb4b4b4}Tb4b4b4,
Ta5d6ff"Ta5d6ffpackets left at the default transport are treated as internal and carry no signal infoTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3positionsTb4b4b4.Te6edf3all Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3hop_start Tff7b72> T79c0ff0 Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffhop_start must be set for hop distance to be derivableTa5d6ff"Tb4b4b4)
T8b949e// Two claims rather than `rx_time ?: 0 > 0`. That form was not unsound — an absent rx_time failed it too —
T8b949e// but it reported a missing timestamp and an epoch-zero one identically.
Te6edf3assertTrueTb4b4b4(Te6edf3positionsTb4b4b4.Te6edf3all Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3rx_time Tff7b72!Tff7b72= Tff7b72null Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffevery reception needs an rx_time; it drives lastHeardTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3positionsTb4b4b4.Te6edf3all Tb4b4b4{ Tb4b4b4(Tffa657itTb4b4b4.Te6edf3rx_time Tff7b72?: T79c0ff0Tb4b4b4) Tff7b72> T79c0ff0 Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffrx_time must be a real timestamp, not epoch zeroTa5d6ff"Tb4b4b4)
T8b949e// Presence and variation are separate claims, and RSSI has to be checked for presence rather than for
T8b949e// "not zero": 0 dBm is a legal (very strong) reading, so `rx_rssi ?: 0` would silently accept a packet
T8b949e// that carries no RSSI at all — the exact sentinel-zero confusion the signal views suffer from.
Te6edf3assertTrueTb4b4b4(Te6edf3positionsTb4b4b4.Te6edf3all Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3rx_rssi Tff7b72!Tff7b72= Tff7b72null Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffevery simulated reception must carry an RSSI readingTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(
Te6edf3positionsTb4b4b4.Te6edf3mapToTb4b4b4(Te6edf3mutableSetOfTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3rx_rssi Tb4b4b4}Tb4b4b4.Te6edf3size Tff7b72> T79c0ff1Tb4b4b4,
Ta5d6ff"Ta5d6ffexpected RSSI to vary between nodes so the signal indicators differTa5d6ff"Tb4b4b4,
Tb4b4b4)
T8b949e// rx_snr has no presence bit in the proto (it defaults to 0f), so variation is all that can be asserted.
Te6edf3assertTrueTb4b4b4(
Te6edf3positionsTb4b4b4.Te6edf3mapToTb4b4b4(Te6edf3mutableSetOfTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3rx_snr Tb4b4b4}Tb4b4b4.Te6edf3size Tff7b72> T79c0ff1Tb4b4b4,
Ta5d6ff"Ta5d6ffexpected SNR to vary between nodes so the signal indicators differTa5d6ff"Tb4b4b4,
Tb4b4b4)
T8b949e// At least one node must look like a direct neighbour, or nothing ever gets a signal reading at all.
Te6edf3assertTrueTb4b4b4(Te6edf3positionsTb4b4b4.Te6edf3any Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3hop_start Tff7b72=Tff7b72= Tffa657itTb4b4b4.Te6edf3hop_limit Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffexpected at least one direct neighbourTa5d6ff"Tb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
T8b949e/**
* Disconnecting has to actually stop the simulator. Its live-telemetry loop, delayed replies and delayed acks all
* run on a scope that outlives the transport, so anything `close()` fails to cancel keeps pushing frames into a
* session the app has already torn down.
*/
Tf0883e@Test
Tff7b72fun Td2a8ff`close stops the simulated mesh`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3callback Tff7b72= Te6edf3RecordingCallbackTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3scope Tff7b72= Te6edf3transportScopeTb4b4b4(Tb4b4b4)
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3transport Tff7b72= Te6edf3MockRadioTransportTb4b4b4(Te6edf3callbackTb4b4b4, Te6edf3scopeTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3CONFIG_NONCETb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3ToRadioTb4b4b4(Te6edf3want_config_id Tff7b72= Te6edf3HandshakeConstantsTb4b4b4.Te6edf3NODE_INFO_NONCETb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)
T8b949e// Drop the handshake frames before timing the seed pass. Asserting on a recorder that still holds them
T8b949e// would pass even if the simulator went silent the moment the handshake ended, which would leave close()
T8b949e// with nothing to stop and this test proving nothing.
Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)
Te6edf3testSchedulerTb4b4b4.Te6edf3advanceTimeByTb4b4b4(Te6edf3SEED_WINDOW_MSTb4b4b4)
Te6edf3assertTrueTb4b4b4(
Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffsanity: the seed pass must be emitting in its own right before close() is exercisedTa5d6ff"Tb4b4b4,
Tb4b4b4)
T8b949e// A text with want_ack leaves both a delayed ack and a delayed reply pending, so close() has more than the
T8b949e// telemetry ticker to cancel.
Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(
Te6edf3ToRadioTb4b4b4(
Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff1Tb4b4b4,
Te6edf3to Tff7b72= Te6edf3BROADCAST_ADDRTb4b4b4,
Te6edf3want_ack Tff7b72= Tff7b72trueTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3TEXT_MESSAGE_APPTb4b4b4, Te6edf3payload Tff7b72= Ta5d6ff"Ta5d6ffpingTa5d6ff"Tb4b4b4.Te6edf3encodeUtf8Tb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4)
Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3clearTb4b4b4(Tb4b4b4)
T8b949e// Several live ticks' worth of virtual time — a surviving ticker would emit on every one of them, and a
T8b949e// surviving reply or ack job would fire well inside the first.
Te6edf3testSchedulerTb4b4b4.Te6edf3advanceTimeByTb4b4b4(Te6edf3LIVE_TICK_MS Tff7b72* Te6edf3LIVE_TICKS_AFTER_CLOSETb4b4b4)
Te6edf3assertTrueTb4b4b4(
Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4,
Ta5d6ff"Ta5d6ffclose() must stop the simulator; got Tffd700${Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff frames afterwardsTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`an open transport delivers the delayed fake acknowledgement`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3callback Tff7b72= Te6edf3RecordingCallbackTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3scope Tff7b72= Te6edf3transportScopeTb4b4b4(Tb4b4b4)
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3transport Tff7b72= Te6edf3MockRadioTransportTb4b4b4(Te6edf3callback Tff7b72= Te6edf3callbackTb4b4b4, Te6edf3scope Tff7b72= Te6edf3scopeTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ffmockTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3outbound Tff7b72= Te6edf3ToRadioTb4b4b4(Te6edf3packet Tff7b72= Te6edf3MeshPacketTb4b4b4(Te6edf3id Tff7b72= T79c0ff7T79c0ff7Tb4b4b4, Te6edf3from Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4Tb4b4b4, Te6edf3want_ack Tff7b72= Tff7b72trueTb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3startTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3outboundTb4b4b4)Tb4b4b4)
Te6edf3advanceTimeByTb4b4b4(Te6edf3ACK_WINDOW_MSTb4b4b4)
Te6edf3runCurrentTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(
Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3any Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3packetTff7b72?.Te6edf3decodedTff7b72?.Te6edf3request_id Tff7b72=Tff7b72= T79c0ff7T79c0ff7 Tb4b4b4}Tb4b4b4,
Ta5d6ff"Ta5d6ffan open mock transport must deliver the fake ACKTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`close is terminal and suppresses delayed acknowledgements`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3callback Tff7b72= Te6edf3RecordingCallbackTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3scope Tff7b72= Te6edf3transportScopeTb4b4b4(Tb4b4b4)
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3transport Tff7b72= Te6edf3MockRadioTransportTb4b4b4(Te6edf3callback Tff7b72= Te6edf3callbackTb4b4b4, Te6edf3scope Tff7b72= Te6edf3scopeTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ffmockTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3outbound Tff7b72= Te6edf3ToRadioTb4b4b4(Te6edf3packet Tff7b72= Te6edf3MeshPacketTb4b4b4(Te6edf3id Tff7b72= T79c0ff7T79c0ff7Tb4b4b4, Te6edf3from Tff7b72= T79c0ff1T79c0ff2T79c0ff3T79c0ff4Tb4b4b4, Te6edf3want_ack Tff7b72= Tff7b72trueTb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3startTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3outboundTb4b4b4)Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4)
Te6edf3advanceTimeByTb4b4b4(Te6edf3ACK_WINDOW_MSTb4b4b4)
Te6edf3runCurrentTb4b4b4(Tb4b4b4)
Te6edf3assertFalseTb4b4b4(
Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3any Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3packetTff7b72?.Te6edf3decodedTff7b72?.Te6edf3request_id Tff7b72=Tff7b72= T79c0ff7T79c0ff7 Tb4b4b4}Tb4b4b4,
Ta5d6ff"Ta5d6ffa delayed fake ACK must not escape after closeTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3outboundTb4b4b4)Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3startTb4b4b4(Tb4b4b4)
Te6edf3runCurrentTb4b4b4(Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(T79c0ff1Tb4b4b4, Te6edf3callbackTb4b4b4.Te6edf3connectsTb4b4b4, Ta5d6ff"Ta5d6ffa closed mock transport must not reconnectTa5d6ff"Tb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`malformed payload is rejected without escaping the admission contract`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3scope Tff7b72= Te6edf3transportScopeTb4b4b4(Tb4b4b4)
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3transport Tff7b72= Te6edf3MockRadioTransportTb4b4b4(Te6edf3callback Tff7b72= Te6edf3RecordingCallbackTb4b4b4(Tb4b4b4)Tb4b4b4, Te6edf3scope Tff7b72= Te6edf3scopeTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ffmockTa5d6ff"Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3startTb4b4b4(Tb4b4b4)
Te6edf3assertFalseTb4b4b4(Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3byteArrayOfTb4b4b4(T79c0ff0Te6edf3x80Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tf0883e@Test
Tff7b72fun Td2a8ff`malformed admin payload is ignored without escaping the admission contract`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3scope Tff7b72= Te6edf3transportScopeTb4b4b4(Tb4b4b4)
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3callback Tff7b72= Te6edf3RecordingCallbackTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3transport Tff7b72= Te6edf3MockRadioTransportTb4b4b4(Te6edf3callback Tff7b72= Te6edf3callbackTb4b4b4, Te6edf3scope Tff7b72= Te6edf3scopeTb4b4b4, Te6edf3address Tff7b72= Ta5d6ff"Ta5d6ffmockTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3outbound Tff7b72=
Te6edf3ToRadioTb4b4b4(
Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= T79c0ff7T79c0ff7Tb4b4b4,
Te6edf3decoded Tff7b72=
Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3ADMIN_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3byteArrayOfTb4b4b4(T79c0ff0Te6edf3x80Tb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3startTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3outboundTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3advanceTimeByTb4b4b4(Te6edf3ACK_WINDOW_MSTb4b4b4)
Te6edf3runCurrentTb4b4b4(Tb4b4b4)
Te6edf3assertFalseTb4b4b4(
Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3any Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3packetTff7b72?.Te6edf3decodedTff7b72?.Te6edf3request_id Tff7b72=Tff7b72= T79c0ff7T79c0ff7 Tb4b4b4}Tb4b4b4,
Ta5d6ff"Ta5d6ffa malformed admin payload must not produce a simulated responseTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3validRequestId Tff7b72= T79c0ff7T79c0ff8
Tff7b72val Te6edf3validAdmin Tff7b72= Te6edf3AdminMessageTb4b4b4(Te6edf3get_config_request Tff7b72= Te6edf3AdminMessageTb4b4b4.Te6edf3ConfigTypeTb4b4b4.Te6edf3LORA_CONFIGTb4b4b4)
Tff7b72val Te6edf3validOutbound Tff7b72=
Te6edf3ToRadioTb4b4b4(
Te6edf3packet Tff7b72=
Te6edf3MeshPacketTb4b4b4(
Te6edf3id Tff7b72= Te6edf3validRequestIdTb4b4b4,
Te6edf3decoded Tff7b72= Te6edf3DataTb4b4b4(Te6edf3portnum Tff7b72= Te6edf3PortNumTb4b4b4.Te6edf3ADMIN_APPTb4b4b4, Te6edf3payload Tff7b72= Te6edf3validAdminTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3toByteStringTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4,
Tb4b4b4)Tb4b4b4,
Tb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3transportTb4b4b4.Te6edf3handleSendToRadioTb4b4b4(Te6edf3validOutboundTb4b4b4.Te6edf3encodeTb4b4b4(Tb4b4b4)Tb4b4b4)Tb4b4b4)
Te6edf3advanceTimeByTb4b4b4(Te6edf3ACK_WINDOW_MSTb4b4b4)
Te6edf3runCurrentTb4b4b4(Tb4b4b4)
Te6edf3assertTrueTb4b4b4(
Te6edf3callbackTb4b4b4.Te6edf3receivedTb4b4b4.Te6edf3any Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3packetTff7b72?.Te6edf3decodedTff7b72?.Te6edf3request_id Tff7b72=Tff7b72= Te6edf3validRequestId Tb4b4b4}Tb4b4b4,
Ta5d6ff"Ta5d6ffa valid admin payload must still produce a simulated responseTa5d6ff"Tb4b4b4,
Tb4b4b4)
Te6edf3transportTb4b4b4.Te6edf3closeTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3scopeTb4b4b4.Te6edf3cancelTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72const Tff7b72val Te6edf3BROADCAST_ADDR Tff7b72= Tff7b72-T79c0ff1
Tff7b72const Tff7b72val Te6edf3MIN_DEMO_NODES Tff7b72= T79c0ff8
T8b949e/** Comfortably longer than the seed pass, but shorter than the first live-telemetry tick. */
Tff7b72const Tff7b72val Te6edf3SEED_WINDOW_MS Tff7b72= T79c0ff1T79c0ff0Te6edf3_000L
T8b949e/** Mirrors `MockRadioTransport.LIVE_TICK_MS`, which is private to the transport. */
Tff7b72const Tff7b72val Te6edf3LIVE_TICK_MS Tff7b72= T79c0ff2T79c0ff0Te6edf3_000L
Tff7b72const Tff7b72val Te6edf3LIVE_TICKS_AFTER_CLOSE Tff7b72= T79c0ff5
Tff7b72const Tff7b72val Te6edf3ACK_WINDOW_MS Tff7b72= T79c0ff3Te6edf3_000L
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.0 - Generated in 0.12s